// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2016-2018, LomboTech Co.Ltd. All rights reserved.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/pm_clock.h>

#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/dmaengine_pcm.h>
#include <mach/debug.h>
#include <mach/csp.h>
#include <mach/common.h>

#include "linux/clk/lombo_clk.h"
#include "csp/n9v1/i2s/csp_i2s.h"
#include "lombo_dmaengine.h"

#undef  MOD_NAME
#define MOD_NAME "LOMBO_I2S"

#define I2S_ON			1
#define I2S_OFF			0

#define CHANNELS_MIN		1
#define CHANNELS_MAX		16

static int audiopll_rate[2] = {CLK_24576M_X6, CLK_225792M_X6};

/* smaple rates supportted ty I2S */
#define LOMBO_I2S_RATES \
	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
	SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
	SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \
	SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_12000 | SNDRV_PCM_RATE_24000)

/* smaple_bits supportted ty I2S */
#define LOMBO_I2S_FORMAT \
	(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | \
	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)

struct lombo_i2s_info {
	struct device	*dev;
	void __iomem	*regs; /* base addr of i2s in the io space */
	struct clk	*i2s_clk;	/* i2s clk */
	struct clk	*parent_clk;
	struct clk	*i2s_apb_gate;
	struct clk	*i2s_apb_reset;
	struct snd_dmaengine_dai_dma_data capture_dma_data;
	struct snd_dmaengine_dai_dma_data playback_dma_data;
	bool is_master_mode;
	u32 tdm_wire;
	u32 wss;
	u32 base_addr;
	unsigned int isplaying;
	unsigned int iscapturing;
};

/* get the i2s_info in the dev associated with the dai */
static inline struct lombo_i2s_info *to_i2s_info(struct snd_soc_dai *dai)
{
	return snd_soc_dai_get_drvdata(dai);
}

/* func to control the i2s tx */
static void lombo_snd_txctrl(struct lombo_i2s_info *i2s_info, int on)
{
	PRT_DBG("Entered\n");
	if (on)
		csp_i2s_tx_open(i2s_info->regs);
	else
		csp_i2s_tx_close(i2s_info->regs);
}

/* func to control the i2s rx */
static void lombo_snd_rxctrl(struct lombo_i2s_info *i2s_info, int on)
{
	PRT_DBG("Entered\n");
	if (on)
		csp_i2s_rx_open(i2s_info->regs);
	else
		csp_i2s_rx_close(i2s_info->regs);
}

/* set i2s m/s mode, i2s data fmt */
static int lombo_i2s_set_format(struct snd_soc_dai *cpu_dai,
		unsigned int fmt)
{
	struct lombo_i2s_info *i2s_info = to_i2s_info(cpu_dai);

	PRT_DBG("Entered\n");
	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
	/* SND_SOC_DAIFMT_CBM_CFM means master for codec,
	 * so it's slave for i2s
	 */
	case SND_SOC_DAIFMT_CBM_CFM:
		csp_i2s_set_ms_mode(i2s_info->regs, I2S_SLAVE);
		i2s_info->is_master_mode = false;
		break;
	case SND_SOC_DAIFMT_CBS_CFS:
		csp_i2s_set_ms_mode(i2s_info->regs, I2S_MASTER);
		i2s_info->is_master_mode = true;
		break;
	default:
		return -EINVAL;
	}
	/* set i2s data fmt */
	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_LEFT_J:
		csp_i2s_set_format(i2s_info->regs, I2S_MSB);
		break;
	case SND_SOC_DAIFMT_I2S:
		csp_i2s_set_format(i2s_info->regs, I2S_STANDARD);
		break;
	case SND_SOC_DAIFMT_RIGHT_J:
		csp_i2s_set_format(i2s_info->regs, I2S_LSB);
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int lombo_i2s_set_tdm_mode(struct lombo_i2s_info *i2sinfo,
				struct snd_pcm_hw_params *params)
{
	unsigned int channel = params_channels(params);

	if (channel == 8)
		i2sinfo->tdm_wire = 1;

	if (channel > 8 && i2sinfo->tdm_wire == 1) {
		PRT_ERR("channel %d.Too many channels.ERR!\n", channel);
		return -EINVAL;
	}
	/* set tdm wire */
	switch (i2sinfo->tdm_wire) {
	case 1:
		csp_i2s_set_tdm_wire(i2sinfo->regs, TDM_WIRE_1);
		break;
	case 2:
		csp_i2s_set_tdm_wire(i2sinfo->regs, TDM_WIRE_2);
		break;
	case 4:
		csp_i2s_set_tdm_wire(i2sinfo->regs, TDM_WIRE_4);
		break;
	default:
		csp_i2s_set_tdm_wire(i2sinfo->regs, TDM_WIRE_1);
		break;
	}
	/* set tdm channel */
	switch (channel / i2sinfo->tdm_wire) {
	case 1:
		csp_i2s_set_tdm_chan(i2sinfo->regs, TDM_CHAN_1);
		break;
	case 2:
		csp_i2s_set_tdm_chan(i2sinfo->regs, TDM_CHAN_2);
		break;
	case 4:
		csp_i2s_set_tdm_chan(i2sinfo->regs, TDM_CHAN_4);
		break;
	case 8:
		csp_i2s_set_tdm_chan(i2sinfo->regs, TDM_CHAN_8);
		break;
	default:
		csp_i2s_set_tdm_chan(i2sinfo->regs, TDM_CHAN_2);
		break;
	}
	return 0;
}

/* set I2S data bitwidth & dma data bitwidth */
static int lombo_i2s_hw_params(struct snd_pcm_substream *substream,
				 struct snd_pcm_hw_params *params,
				 struct snd_soc_dai *dai)
{
	struct snd_dmaengine_dai_dma_data *dma_data;
	struct lombo_i2s_info *i2s_info;
	int tdm;
	int bclk_div;
	int ret = 0;

	i2s_info = to_i2s_info(dai);
	dma_data = snd_soc_dai_get_dma_data(dai, substream);
	/* set i2s wlen, dma data width according to bitwidth */
	switch (params_format(params)) {
	case SNDRV_PCM_FORMAT_U8:
		csp_i2s_set_wlen(i2s_info->regs, 8, substream->stream);
		dma_data->addr_width = 2;
		break;
	case SNDRV_PCM_FORMAT_S16_LE:
		csp_i2s_set_wlen(i2s_info->regs, 16, substream->stream);
		dma_data->addr_width = 2;
		break;
	case SNDRV_PCM_FORMAT_S24_LE:
		csp_i2s_set_wlen(i2s_info->regs, 24, substream->stream);
		dma_data->addr_width = 4;
		break;
	case SNDRV_PCM_FORMAT_S32_LE:
		csp_i2s_set_wlen(i2s_info->regs, 32, substream->stream);
		dma_data->addr_width = 4;
		break;
	default:
		return -EINVAL;
	}

	/* set tdm mode */
	ret = lombo_i2s_set_tdm_mode(i2s_info, params);

	/* calculate bclk div and set the div */
	tdm = params_channels(params) / i2s_info->tdm_wire;
	bclk_div = clk_get_rate(i2s_info->i2s_clk) /
		(tdm * i2s_info->wss * params_rate(params)) - 1;

	csp_i2s_set_div(i2s_info->regs, LOMBO_DIV_BCLK, bclk_div);

	PRT_DBG("dma_data->addr_width %d, bclk div %d\n",
		dma_data->addr_width, bclk_div);
	return ret;
}

#ifdef CONFIG_ARCH_LOMBO_N7V3
static int lombo_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
			       struct snd_soc_dai *dai)
{
	int ret = 0;
	struct lombo_i2s_info *i2s_info = to_i2s_info(dai);

	PRT_DBG("entered\n");

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		PRT_DBG("trigger start\n");
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
			PRT_DBG("iscapturing %d\n", i2s_info->iscapturing);
			if (i2s_info->iscapturing) {
				lombo_snd_txctrl(i2s_info, I2S_ON);
			} else {
				lombo_snd_rxctrl(i2s_info, I2S_ON);
				lombo_snd_txctrl(i2s_info, I2S_ON);
			}
			i2s_info->isplaying = 1;
		} else {
			PRT_DBG("isplaying %d\n", i2s_info->isplaying);
			if (!i2s_info->isplaying)
				lombo_snd_rxctrl(i2s_info, I2S_ON);
			i2s_info->iscapturing = 1;
		}
		break;
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		PRT_DBG("trigger stop\n");
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
			PRT_DBG("iscapturing %d\n", i2s_info->iscapturing);
			if (i2s_info->iscapturing) {
				lombo_snd_txctrl(i2s_info, I2S_OFF);
			} else {
				lombo_snd_rxctrl(i2s_info, I2S_OFF);
				lombo_snd_txctrl(i2s_info, I2S_OFF);
			}
			i2s_info->isplaying = 0;
		} else {
			PRT_DBG("isplaying %d\n", i2s_info->isplaying);
			if (!i2s_info->isplaying)
				lombo_snd_rxctrl(i2s_info, I2S_OFF);
			i2s_info->iscapturing = 0;
		}
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

#else

/* func to trigger transmit */
static int lombo_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
			       struct snd_soc_dai *dai)
{
	int ret = 0;
	struct lombo_i2s_info *i2s_info = to_i2s_info(dai);

	PRT_DBG("entered\n");

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		PRT_DBG("trigger start\n");
		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			lombo_snd_rxctrl(i2s_info, I2S_ON);
		else
			lombo_snd_txctrl(i2s_info, I2S_ON);
		break;
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		PRT_DBG("trigger stop\n");
		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			lombo_snd_rxctrl(i2s_info, I2S_OFF);
		else
			lombo_snd_txctrl(i2s_info, I2S_OFF);
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

#endif

/* set i2s clk divider */
static int lombo_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
	int div_id, int div)
{
	struct lombo_i2s_info *i2s_info = to_i2s_info(cpu_dai);

	PRT_DBG("set clk div id %d, div %d\n", div_id, div);
	csp_i2s_set_div(i2s_info->regs, div_id, div);
	return 0;
}

/* set the i2s clk and clk rate */
static int lombo_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
	int clk_id, unsigned int freq, int dir)
{
	int ret = 0;
	struct lombo_i2s_info *i2s_info = to_i2s_info(cpu_dai);

	PRT_DBG("clkid %d, rate %d\n", clk_id, freq);

	/* don't need to set clk for slave mode */
	if (!i2s_info->is_master_mode)
		return ret;
	/* set rate for audio pll clk */
	ret = clk_set_rate(i2s_info->parent_clk, audiopll_rate[clk_id]);
	if (ret)
		PRT_ERR("Fail to set clk parent %d\n", ret);

	ret = clk_set_rate(i2s_info->i2s_clk, freq);
	if (ret)
		dev_err(i2s_info->dev, "Fail to set clk %d\n", ret);

	return ret;
}

/* i2s dai probe func */
static int lombo_i2s_dai_probe(struct snd_soc_dai *dai)
{
	struct lombo_i2s_info *i2s_info = to_i2s_info(dai);

	if (i2s_info == NULL) {
		PRT_ERR("i2s_info null error !!\n");
		return -1;
	}
	PRT_DBG("cpu dai %s\n", dai->name);
	/* set dma params */
	dai->capture_dma_data = &i2s_info->capture_dma_data;
	dai->playback_dma_data = &i2s_info->playback_dma_data;

	pm_runtime_get_sync(dai->dev);
	/* enable i2s module */
	csp_i2s_enable(i2s_info->regs);

	/* set the wss to default value 32 */
	csp_i2s_set_wss(i2s_info->regs, i2s_info->wss);
	pm_runtime_put(dai->dev);

	return 0;
}

/* define dai ops */
static const struct snd_soc_dai_ops lombo_i2s_dai_ops = {
	.trigger	= lombo_i2s_trigger,
	.hw_params	= lombo_i2s_hw_params,
	.set_fmt	= lombo_i2s_set_format,
	.set_clkdiv	= lombo_i2s_set_clkdiv,
	.set_sysclk	= lombo_i2s_set_sysclk,
};

/* define I2S dai driver */
static struct snd_soc_dai_driver lombo_i2s_dai = {
	.probe		= lombo_i2s_dai_probe,
	.playback = {
		.stream_name = "Playback",
		.channels_min = CHANNELS_MIN,
		.channels_max = CHANNELS_MAX,
		.rates = LOMBO_I2S_RATES,
		.formats = LOMBO_I2S_FORMAT,
	},
	.capture = {
		.stream_name = "Capture",
		.channels_min = CHANNELS_MIN,
		.channels_max = CHANNELS_MAX,
		.rates = LOMBO_I2S_RATES,
		.formats = LOMBO_I2S_FORMAT,
	},
	.ops = &lombo_i2s_dai_ops,
};

static const struct snd_soc_component_driver lombo_i2s_component = {
	.name		= "lombo-i2s",
};

/* i2s platform driver probe func */
static int lombo_i2s_dev_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct lombo_i2s_info *i2s_info;
	struct resource *res;

	PRT_DBG("Entered\n");

	i2s_info = devm_kzalloc(&pdev->dev, sizeof(*i2s_info), GFP_KERNEL);
	if (!i2s_info)
		return -ENOMEM;

	/* get platform device resource */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "can't get io resources\n");
		return -ENOENT;
	}

	/* ioremap the registers */
	i2s_info->base_addr = res->start;
	i2s_info->regs = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(i2s_info->regs))
		return PTR_ERR(i2s_info->regs);

	/* set pcm dma addr & bustwidth */
	i2s_info->capture_dma_data.addr = i2s_info->base_addr +
						csp_i2s_dmaaddr_in();
	i2s_info->playback_dma_data.addr = i2s_info->base_addr +
						csp_i2s_dmaaddr_out();
	i2s_info->capture_dma_data.maxburst = 4;
	i2s_info->playback_dma_data.maxburst = 4;
	i2s_info->capture_dma_data.addr_width = 2;
	i2s_info->playback_dma_data.addr_width = 2;

	/*
	 * dev_info(&pdev->dev, "get i2s capture dma address %llx\n",
		i2s_info->capture_dma_data.addr);
	*/
	i2s_info->i2s_apb_gate = devm_clk_get(&pdev->dev, "i2s_apb_gate");
	if (IS_ERR(i2s_info->i2s_apb_gate)) {
		dev_err(&pdev->dev, "get i2s_apb_gate error\n");
		return PTR_ERR(i2s_info->i2s_apb_gate);
	}
#ifdef CONFIG_PM_GENERIC_DOMAINS
	ret = pm_clk_create(&pdev->dev);
	ret |= pm_clk_add_clk(&pdev->dev, i2s_info->i2s_apb_gate);
#else
	ret = clk_prepare_enable(i2s_info->i2s_apb_gate);
#endif
	if (ret) {
		dev_err(&pdev->dev, "enable i2s_apb_gate error\n");
		return ret;
	}
	i2s_info->i2s_apb_reset = devm_clk_get(&pdev->dev, "i2s_apb_reset");
	if (IS_ERR(i2s_info->i2s_apb_reset)) {
		dev_err(&pdev->dev, "get i2s_apb_reset error\n");
		return PTR_ERR(i2s_info->i2s_apb_reset);
	}
	ret = clk_prepare_enable(i2s_info->i2s_apb_reset);
	if (ret) {
		dev_err(&pdev->dev, "enable i2s_apb_reset error\n");
		return ret;
	}

	/* get i2s-clock and enable it */
	i2s_info->i2s_clk = devm_clk_get(&pdev->dev, "i2s_module_clk");
	if (IS_ERR(i2s_info->i2s_clk)) {
		dev_err(&pdev->dev, "get i2s-clk error\n");
		return PTR_ERR(i2s_info->i2s_clk);
	}
#ifdef CONFIG_PM_GENERIC_DOMAINS
	ret = pm_clk_add_clk(&pdev->dev, i2s_info->i2s_clk);
	ret |= pm_clk_resume(&pdev->dev);
#else
	ret = clk_prepare_enable(i2s_info->i2s_clk);
#endif
	if (ret) {
		dev_err(&pdev->dev, "enable i2s-clk error\n");
		return ret;
	}

	i2s_info->parent_clk = clk_get(NULL, "audio_pll");
	if (IS_ERR(i2s_info->parent_clk)) {
		dev_err(&pdev->dev, "get audio_pll error\n");
		return PTR_ERR(i2s_info->parent_clk);
	}

	clk_pll_set_mode(i2s_info->parent_clk, PLL_MODE_FRAC);
	ret = clk_set_rate(i2s_info->parent_clk, audiopll_rate[0]);
	if (ret)
		dev_err(&pdev->dev, "Fail to set clk parent %d\n", ret);

	ret = clk_set_parent(i2s_info->i2s_clk, i2s_info->parent_clk);
	if (ret)
		dev_err(&pdev->dev, "Fail to set clk parent %d\n", ret);

	/* set default clock rate */
	clk_set_rate(i2s_info->i2s_clk, CLK_24576M_X2);

#ifdef CONFIG_PM_GENERIC_DOMAINS
	pm_runtime_set_autosuspend_delay(&pdev->dev, 100);
	pm_runtime_use_autosuspend(&pdev->dev);
	/* set pm_runtime active */
	pm_runtime_set_active(&pdev->dev);
	/* enable pm_runtime */
	pm_runtime_enable(&pdev->dev);
#endif

	/* Get tdm wire */
	ret = device_property_read_u32(&pdev->dev, "tdm_wire",
			&i2s_info->tdm_wire);
	if (ret || ((i2s_info->tdm_wire != 1) &&
		(i2s_info->tdm_wire != 2) &&
		(i2s_info->tdm_wire != 4))) {
		i2s_info->tdm_wire = 1;
		PRT_INFO("get error wire, set to default 1!!");
	}
	ret = device_property_read_u32(&pdev->dev, "wss", &i2s_info->wss);
	if (ret)
		i2s_info->wss = 32;

	/*
	 * the i2s_info will be set to dev, and dai also have the pointer
	 * to dev, so we can't get i2s_info from dai later
	 */
	dev_set_drvdata(&pdev->dev, i2s_info);

	/* register i2s dai */
	lombo_i2s_dai.name = dev_name(&pdev->dev);
	ret = snd_soc_register_component(&pdev->dev, &lombo_i2s_component,
					 &lombo_i2s_dai, 1);
	if (ret) {
		PRT_ERR("failed to register the dai\n");
		return ret;
	}

	/* register pcm platform driver */
	ret = lombo_dmaengine_pcm_register(&pdev->dev);
	if (ret) {
		PRT_ERR("failed to register the dmaengine: %d !!\n", ret);
		goto err;
	}
	dev_dbg(&pdev->dev, "i2s probe is ok!\n");

	return 0;
err:
	snd_soc_unregister_component(&pdev->dev);
	return ret;
}

static int lombo_i2s_dev_remove(struct platform_device *pdev)
{
	struct lombo_i2s_info *i2s_info = platform_get_drvdata(pdev);

	/* release the parent clk */
	clk_put(i2s_info->parent_clk);

	snd_soc_unregister_component(&pdev->dev);
	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int  pm_i2s_suspend(struct device *dev)
{
	struct lombo_i2s_info *i2s_info = dev_get_drvdata(dev);

	PRT_INFO("====enter=====\n");
	clk_disable_unprepare(i2s_info->i2s_apb_reset);
#ifndef CONFIG_PM_GENERIC_DOMAINS
	clk_disable_unprepare(i2s_info->i2s_apb_gate);
	clk_disable_unprepare(i2s_info->i2s_clk);
#endif

	return 0;
}

static int  pm_i2s_resume(struct device *dev)
{
	struct lombo_i2s_info *i2s_info = dev_get_drvdata(dev);

	PRT_INFO("====enter=====\n");
	clk_prepare_enable(i2s_info->i2s_apb_reset);

#ifndef CONFIG_PM_GENERIC_DOMAINS
	clk_prepare_enable(i2s_info->i2s_apb_gate);
	clk_prepare_enable(i2s_info->i2s_clk);
	csp_i2s_enable(i2s_info->regs);
	csp_i2s_set_wss(i2s_info->regs, i2s_info->wss);
#else
	if (pm_runtime_status_suspended(dev)) {
		pm_clk_resume(dev);
		csp_i2s_enable(i2s_info->regs);
		csp_i2s_set_wss(i2s_info->regs, i2s_info->wss);
	}
	if (pm_runtime_status_suspended(dev))
		pm_clk_suspend(dev);
#endif

	return 0;
}

#endif

#ifdef CONFIG_PM
static const struct dev_pm_ops lombo_i2s_dev_pm_ops = {
#ifdef CONFIG_PM_SLEEP
	SET_SYSTEM_SLEEP_PM_OPS(pm_i2s_suspend,
		pm_i2s_resume)
#endif
};

#define LOMBO_DEV_PM_OPS (&lombo_i2s_dev_pm_ops)
#else
#define LOMBO_DEV_PM_OPS NULL
#endif


static const struct of_device_id lombo_i2s_match[] = {
	{ .compatible = "lombo, n9-i2s0", },
	{ .compatible = "lombo, n9-i2s1", },
	{ .compatible = "lombo, n9-i2s2", },
	{ .compatible = "lombo, n9-i2s3", },
	{ .compatible = "lombo, n7v3-i2s0", },
	{ .compatible = "lombo, n7v5-i2s0", },
	{ .compatible = "lombo, n5v1-i2s0", },
	{ /* end */},
};

static struct platform_driver lombo_i2s_driver = {
	.probe  = lombo_i2s_dev_probe,
	.remove = lombo_i2s_dev_remove,
	.driver = {
		.name = "lombo-i2s",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(lombo_i2s_match),
		.pm = LOMBO_DEV_PM_OPS,
	},
};

module_platform_driver(lombo_i2s_driver);

/* Module information */
MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("lombo I2S SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:lombo-i2s");

